package cn.pm.datasource.sharding.utils;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.redis.connection.RedisStringCommands;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.core.types.Expiration;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;

/**
 * Create by spf on 2018/12/03
 * 分布式锁 工具类
 */
@Component
public class LockUtil {

    private static final Logger log = LoggerFactory.getLogger(LockUtil.class);
    private static final String LOCK_SUCCESS = "true";
    private static final String SET_IF_NOT_EXIST = "NX";
    private static final String SET_WITH_EXPIRE_TIME = "PX";
    private static final ReentrantLock localLock = new ReentrantLock();
    //锁前缀
    private static final String LOCK_PREFIX = "LOCK:";

    //默认请求id
    private static final String DEFAULT_VALUE = "default_value";
    //默认失效时间
    private static final Integer DEFAULT_EXPIRE_TIME = 30;
    private static RedisTemplate redisTemplate;
    @Resource
    private void setRedisTemplate(RedisTemplate redisTemplate) {
        LockUtil.redisTemplate = redisTemplate;
    }

    public static boolean lock(String lockKey, String requiredId, int expireTime,boolean localhost)  {
        try {
            return localLock.tryLock(expireTime, TimeUnit.MILLISECONDS);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return false;
    }
    public static void unlock(boolean localhost) {
        localLock.unlock();
    }
    /**
     * 加锁
     *
     * @param lockKey    锁
     * @param requiredId 请求标识
     * @param expireTime 超期时间  s
     * @return 是否获取成功
     */
    public static boolean lock(String lockKey, String requiredId, int expireTime) {
        try {

            /**
             * SpringBoot 2.0之后 spring-boot-starter-data-redis底层采用lettuce连接redis
             * 所以如果想要使用SpringBoot封装的Redis连接，将无法获取Jedis和JedisCluster
             * 解决方案是调整分布式锁实现方式为新的实现方式
             */
            /*if (isJedis() == null) {
                throw new RuntimeException("redis链接获取失败");
            }
            String result = "";
            if (isJedis()) {
                Jedis jedis = (Jedis) redisConnection.getNativeConnection();
                result = jedis.set(RedisUtil.getPrefix() + LOCK_PREFIX + key, requiredId, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireTime);
            } else {
                JedisCluster cluster = (JedisCluster) redisConnection.getNativeConnection();
                result = cluster.set(RedisUtil.getPrefix() + LOCK_PREFIX + key, requiredId, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireTime);
            }
            */

            Object result = redisTemplate.execute((RedisCallback<Boolean>) connection ->
                    connection.set((RedisUtil.getPrefix() + LOCK_PREFIX + lockKey).getBytes(),
                            requiredId.getBytes(),
                            Expiration.from(expireTime, TimeUnit.SECONDS),
                            RedisStringCommands.SetOption.SET_IF_ABSENT));

            if (result != null && LOCK_SUCCESS.equals(result.toString())) {
                return true;
            }
            return false;
        } catch (Exception e) {
            log.error("分布式锁加锁失败  key:" + lockKey + " required:" + requiredId, e);
            return false;
        }
    }

    //默认30秒
    public static boolean lock(String key, String requiredId) {
        return lock(key, requiredId, DEFAULT_EXPIRE_TIME);
    }

    //不添加请求id
    public static boolean lock(String key,int expireTime) {
        return lock(key, DEFAULT_VALUE, expireTime);
    }

    //不添加请求id 进行加锁（不安全，不推荐）
    public static boolean lock(String key) {
        return lock(key, DEFAULT_VALUE, DEFAULT_EXPIRE_TIME);
    }

    /**
     * 分布式锁   如果加锁失败将尝试休眠后重新尝试
     * @param tryCount  重试次数
     * @param sleepMillis 加锁失败时，休眠多久后再次重试  毫秒
     */
    public static boolean lockTryAgain(String lockKey, String requiredId, int expireTime, int tryCount, long sleepMillis) {
        for (int i = 0; i < tryCount; i++) {
            boolean lock = lock(lockKey, requiredId, expireTime);
            if (lock) {
                return lock;
            } else {
                try {
                    Thread.sleep(sleepMillis);
                } catch (InterruptedException ignored) {}
            }
        }
        return false;
    }
    public static boolean lockTryAgain(String lockKey, String requiredId, int tryCount, long sleepMillis) {
        return lockTryAgain(lockKey, requiredId, DEFAULT_EXPIRE_TIME, tryCount, sleepMillis);
    }
    public static boolean lockTryAgain(String lockKey, int tryCount, long sleepMillis) {
        return lockTryAgain(lockKey, DEFAULT_VALUE, DEFAULT_EXPIRE_TIME, tryCount, sleepMillis);
    }

    /**
     * 释放锁
     *
     * @param lockKey   锁
     * @param requestId 请求标识
     * @return 是否释放成功
     */
    public static boolean unLock(String lockKey, String requestId) {
        String scriptStr = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
        DefaultRedisScript<Long> script = new DefaultRedisScript(scriptStr, Long.class);
        List<String> keys = new ArrayList<>();
        keys.add(RedisUtil.getPrefix() + LOCK_PREFIX + lockKey);
        try {
           /* Object result = null;
            if (isJedis() == null) {
                throw new RuntimeException("redis链接获取失败");
            }
            if (isJedis()) {
                Jedis jedis = (Jedis) redisConnection.getNativeConnection();
                result = jedis.eval(script, Collections.singletonList(RedisUtil.getPrefix() + LOCK_PREFIX + lockKey), Collections.singletonList(requestId));
            } else {
                JedisCluster cluster = (JedisCluster) redisConnection.getNativeConnection();
                result = cluster.eval(script, Collections.singletonList(RedisUtil.getPrefix() + LOCK_PREFIX + lockKey), Collections.singletonList(requestId));
            }*/

            Object result = redisTemplate.execute(script, new StringRedisSerializer(), new RedisSerializer<Long>() {
                private final Charset charset = Charset.forName("UTF8");

                @Override
                public byte[] serialize(Long aLong) {
                    return (aLong == null ? null : (aLong.toString()).getBytes(charset));
                }

                @Override
                public Long deserialize(byte[] bytes) {
                    return (bytes == null ? null : Long.parseLong(new String(bytes, charset)));
                }
            }, keys, requestId);

            if (result == null) {
                return false;
            }
            long rt = 0;
            rt = (Long) result;
            if (rt > 0) {
                return true;
            }
            return false;
        } catch (Exception e) {
            log.error("分布式锁解锁失败！！  lockKey:" + lockKey + " requestId:" + requestId, e);
            return false;
        }
    }

    // 使用默认请求id加锁（不安全，不推荐）
    public static boolean unLock(String key) {
        return unLock(key, DEFAULT_VALUE);
    }

}